Skip to content

perf(fair2): vectorise forcing-aggregation output extraction (~9x at 842 members)#109

Merged
benmsanderson merged 2 commits into
feat/fair2-ciceroscmpy2-adapters-and-runmode-nonforkfrom
perf/fair2-vectorise-output-extraction
Jul 2, 2026
Merged

perf(fair2): vectorise forcing-aggregation output extraction (~9x at 842 members)#109
benmsanderson merged 2 commits into
feat/fair2-ciceroscmpy2-adapters-and-runmode-nonforkfrom
perf/fair2-vectorise-output-extraction

Conversation

@benmsanderson

Copy link
Copy Markdown
Collaborator

What

FaIR2 native-calibration ensemble runs were the throughput bottleneck for the AR7 Ch5 chapter (~370 s/pathway at 842 members, single core, superlinear). Profiling (py-spy, ssp245, 400–842 members) pinned the cost:

  • output extractor forcing aggregations: ~68% — repeated xarray .sel(specie=…).isel(scenario, config) orthogonal indexing, run once per (scenario, member) for every aggregation recipe.
  • FaIR's actual integration: ~6%. Conc-driven unstep_concentration: ~0.3%.

So the hotspot is Python/xarray output handling, not FaIR's solver and not the conc-driven path (measured conc-driven 14.3 s ≈ emissions-driven 14.5 s at 100 members).

Fix

Materialise f.forcing to a numpy array once with a species→index map, and do the per-member sums / single-species lookups with integer indexing instead of per-member xarray orthogonal indexing. Same species order and summation order → numerically identical.

Benchmarks (ssp245, native AR7 1.6.0 calibration, single core)

members before after speedup
200 23.9 s 14.4 s 1.7×
400 63.9 s 21.5 s 3.0×
842 ~370 s 39.6 s ~9×

Superlinear scaling is removed (now ~linear in members).

Equivalence

GSAT, ERF|Greenhouse Gases and ERF|Aerosols (ensemble mean and std) match the previous xarray path to 6 d.p. Added a unit equivalence test (tests/unit/adapters/test_fair2_output_extractor.py) asserting the numpy aggregation path equals a direct xarray reduction; the existing test_modern_adapters.py FaIR2 snapshot is unchanged. No FaIR internals touched, MAGICC/CICERO/conc-driven paths untouched.

🤖 Generated with Claude Code

benmsanderson and others added 2 commits July 2, 2026 12:47
Profiling a native-calibration ensemble run (py-spy, ssp245, 400-842
members) showed ~68% of wall time in the output extractor's forcing
aggregations -- specifically repeated xarray `.sel(specie=...).isel(...)`
orthogonal indexing, run once per (scenario, member) for every
aggregation. FaIR's actual integration was ~6%, and the conc-driven
`unstep_concentration` path ~0.3% (so conc-vs-emissions is not the
cost).

Materialise `f.forcing` to a numpy array once (species->index map) and
do the per-member sums / single-species lookups with integer indexing.
Same species order and summation order, so results are numerically
identical.

Benchmarks (ssp245, native AR7 1.6.0 calibration, single core):

  members   before     after
  200       23.9 s     14.4 s
  400       63.9 s     21.5 s
  842       ~370 s     39.6 s   (~9x; superlinear scaling removed)

GSAT, ERF|Greenhouse Gases and ERF|Aerosols (ensemble mean AND std)
match the previous xarray path to 6 d.p. Adds a unit equivalence test
(numpy aggregation == direct xarray reduction) and the existing
modern-adapter snapshot is unchanged. No FaIR internals touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@benmsanderson benmsanderson merged commit bdeb4e7 into feat/fair2-ciceroscmpy2-adapters-and-runmode-nonfork Jul 2, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant